﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO.Packaging;
using System.IO;
using BizKit.TextDiff;

namespace OPCDiffCore.Text
{
    /// <summary>
    /// Implementation of a text analyzer.
    /// </summary>
    class TextAnalyzer : PartAnalyzer
    {
        internal override PartDiff Analyze(PackagePart originalPart, PackagePart diffPart, AnalyzeLevel level)
        {
            PartDiff retDiff = new PartDiff(originalPart.Uri, originalPart.ContentType);
            TextDiffer txtDiffer = new TextDiffer();

            StreamReader srOrig = new StreamReader(originalPart.GetStream());
            StreamReader srDiff = new StreamReader(diffPart.GetStream());
            long[] arr1 = BuilDiffArray(srOrig);
            long[] arr2 = BuilDiffArray(srDiff);

            if (txtDiffer.Execute(arr1, arr2))
            {
                // Some changes in the text
                foreach (ChangeRec change in txtDiffer.ChangeList)
                {
                    LineChange lineChanges = BuildLineChangeFromChangeRec(change, originalPart.GetStream(), diffPart.GetStream());

                    retDiff.Add(
                        new DiffElement(
                            GetDiffTypeFromChangeType(change.Kind),
                            DiffElementType.TextContent,
                            GetDescriptionFromLineChange(lineChanges),
                            lineChanges)
                            );
                }
            }

            return retDiff;
        }

        private LineChange BuildLineChangeFromChangeRec(ChangeRec change, Stream sOrig, Stream sDiff)
        {
            LineChange retChange = new LineChange()
            {
                DiffType = GetDiffTypeFromChangeType(change.Kind),
                x = change.x + 1,
                y = change.y + 1,
                Range = change.Range
            };

            /* Find line changes */
            string s, s1 = "", s2 = "";
            int cLine = 0;

            // Get original line
            StreamReader srOrig = new StreamReader(sOrig);
            while (!String.IsNullOrEmpty((s = srOrig.ReadLine())))
            {
                if (cLine == change.x)
                {
                    s1 = s;
                    break;
                }
                ++cLine;
            }

            cLine = 0;
            // Get diff line
            StreamReader srDiff = new StreamReader(sDiff);
            while (!String.IsNullOrEmpty((s = srDiff.ReadLine())))
            {
                if (cLine == change.y)
                {
                    s2 = s;
                    break;
                }
                ++cLine;
            }

            if (String.IsNullOrEmpty(s1) || String.IsNullOrEmpty(s2))
                throw new InvalidProgramException("Can't retrive a diff content! This would never occur !");

            TextDiffer textDiff = new TextDiffer();
            textDiff.Execute(s1, s2);

            // Check the changes
            if (textDiff.ChangeCount > 0)
            {
                foreach (ChangeRec c in textDiff.ChangeList)
                {
                    switch (c.Kind)
                    {
                        case ChangeKind.Modify:
                            retChange.Changes.Add(
                                new InternalLineChange(
                                    c.x,
                                    c.x + c.Range,
                                    GetDiffTypeFromChangeType(c.Kind),
                                    s1.Substring(c.x, c.Range),
                                    s2.Substring(c.y, c.Range)
                                    ));
                            break;

                        case ChangeKind.Add:
                            retChange.Changes.Add(
                                new InternalLineChange(
                                    c.y,
                                    c.y + c.Range,
                                    GetDiffTypeFromChangeType(c.Kind),
                                    "",
                                    s2.Substring(c.y, c.Range)
                                    ));
                            break;

                        case ChangeKind.Delete:
                            retChange.Changes.Add(
                                new InternalLineChange(
                                    c.x,
                                    c.x + c.Range,
                                    GetDiffTypeFromChangeType(c.Kind),
                                    s1.Substring(c.x, c.Range),
                                    ""
                                    )
                                );
                            break;
                    }
                }
            }

            return retChange;
        }

        private string GetDescriptionFromLineChange(LineChange lineChange)
        {
            switch (lineChange.DiffType)
            {
                case DiffType.Added:
                    return String.Format("Line(s) added at {0}{1}",
                        lineChange.x,
                        lineChange.Range > 1 ? " to {0}" + (lineChange.x + lineChange.Range) : "");

                case DiffType.Removed:
                    return String.Format("Line(s) {0}{1} removed",
                        lineChange.x,
                        lineChange.Range > 1 ? " to {0}" + (lineChange.x + lineChange.Range) : "");

                case DiffType.Modified:
                    return String.Format("Line(s) {0}{1} modified",
                        lineChange.x,
                        lineChange.Range > 1 ? " to {0}" + (lineChange.x + lineChange.Range) : "");

                default:
                    return "";
            }
        }

        private DiffType GetDiffTypeFromChangeType(ChangeKind kind)
        {
            switch (kind)
            {
                case ChangeKind.None:
                    return DiffType.Unchanged;

                case ChangeKind.Add:
                    return DiffType.Added;

                case ChangeKind.Delete:
                    return DiffType.Removed;

                case ChangeKind.Modify:
                    return DiffType.Modified;

                default:
                    throw new InvalidOperationException("Text change can't have an unknown state !");
            }
        }

        private long[] BuilDiffArray(StreamReader sReader)
        {
            List<long> retArr = new List<long>();
            string s;
            while (!String.IsNullOrEmpty((s = sReader.ReadLine())))
                retArr.Add(s.GetHashCode());

            return retArr.ToArray();
        }
    }
}
